/**
 * 
 */
package com.huxi.lang.local;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 日期法定节假日策略
 * 
 * @since 1.0
 * @version 1.0
 * @author jian.wu
 */
public interface FestivalStrategy {

	/**
	 * 节假日调休状态
	 * 
	 * @since 1.0
	 * @version 1.0
	 * @author jian.wu
	 */
	static enum FestivalState {
		/**
		 * 法定节假日休息日
		 * 
		 * @since 1.0
		 */
		HOLIDAY_FEAST(true),
		/**
		 * 法定节假日调班工作日
		 * 
		 * @since 1.0
		 */
		WEEKDAY_FEAST(false),
		/**
		 * 正常休息日，如非法定节假日的周末
		 * 
		 * @since 1.0
		 */
		HOLIDAY(true),
		/**
		 * 正常工作日
		 * 
		 * @since 1.0
		 */
		WEEKDAY(false);

		private final boolean holiday;

		private FestivalState(boolean holiday) {
			this.holiday = holiday;
		}

		public boolean isHoliday() {
			return holiday;
		}
	}

	/**
	 * 本地日期的节假日调休状态
	 * 
	 * @param localDate 本地日期，非空。
	 * @return 节假日调休状态，不存在则返回空值。
	 * @throws NullPointerException  非空参数为空
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	FestivalState getFestivalDate(LocalDate localDate) throws IllegalStateException;

	/**
	 * 本地日期是否休息日
	 * 
	 * @param localDate 本地日期，非空。
	 * @return 休息日返回真，否则返回假。
	 * @throws NullPointerException  非空参数为空
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default boolean isFestival(LocalDate localDate) throws IllegalStateException {
		FestivalState st = getFestivalDate(localDate);
		return st != null ? st.isHoliday() : DateUtils.isWeekend(localDate);
	}

	/**
	 * 本地日期是否工作日
	 * 
	 * @param onlyWeekday 工作日是否必须为周一、周二、周三、周四、周五。
	 * @param localDate   本地日期，非空。
	 * @return 工作日返回真，否则返回假。
	 * @throws NullPointerException  非空参数为空
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default boolean isWeekday(boolean onlyWeekday, LocalDate localDate) throws IllegalStateException {
		if (onlyWeekday && DateUtils.isWeekend(localDate)) {
			return false;
		}
		FestivalState st = getFestivalDate(localDate);
		if (st != null) {
			return !st.isHoliday();
		}
		return onlyWeekday || !DateUtils.isWeekend(localDate);
	}

	/**
	 * 求本地日期的最近工作日
	 * 
	 * @param onlyWeekday 工作日是否必须为周一、周二、周三、周四、周五。
	 * @param localDate   本地日期，非空。
	 * @param after       求最近工作的移动方向</br>
	 *                    true: 返回日期大于或等于参数日期</br>
	 *                    false: 返回日期小于或等于参数日期</br>
	 * @return 最近工作日，非空。<br>
	 *         本地日期为工作日则直接返回本地日期，否则向前/后移动返回最近的工作日。
	 * @throws NullPointerException  非空参数值为空时，抛出此异常。
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default LocalDate weekday(boolean onlyWeekday, LocalDate localDate, boolean after) throws IllegalStateException {
		if (isWeekday(onlyWeekday, localDate)) {
			return localDate;
		}
		if (after) {
			LocalDate nextDate = localDate.plusDays(1);
			while (!isWeekday(onlyWeekday, nextDate)) {
				nextDate = nextDate.plusDays(1);
			}
			return nextDate;
		} else {
			LocalDate prefDate = localDate.minusDays(1);
			while (!isWeekday(onlyWeekday, prefDate)) {
				prefDate = prefDate.minusDays(1);
			}
			return prefDate;
		}
	}

	/**
	 * 求本地日期区间的工作日列表
	 * 
	 * @param onlyWeekday 工作日是否必须为周一、周二、周三、周四、周五。
	 * @param begin       开始日期，包括。
	 * @param end         结束日期，不包括。
	 * @return 工作日列表，非空。
	 * @throws NullPointerException  非空参数值为空时，抛出此异常。
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default List<LocalDate> weekdays(boolean onlyWeekday, LocalDate begin, LocalDate end) throws IllegalStateException {
		Objects.requireNonNull(begin);
		Objects.requireNonNull(end);
		ArrayList<LocalDate> list = new ArrayList<>();
		while (begin.isBefore(end)) {
			if (isWeekday(onlyWeekday, begin)) {
				list.add(begin);
			}
			begin = begin.plusDays(1);
		}
		return list;
	}

	/**
	 * 求大于本地日期的最近工作日，函数返回日期大于参数日期。
	 * 
	 * @param onlyWeekday 工作日是否必须为周一、周二、周三、周四、周五。
	 * @param localDate   本地日期，非空。
	 * @return 最近工作日，非空。
	 * @throws NullPointerException  非空参数值为空时，抛出此异常。
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default LocalDate after(boolean onlyWeekday, LocalDate localDate) throws IllegalStateException {
		Objects.requireNonNull(localDate);
		return weekday(onlyWeekday, localDate.plusDays(1), true);
	}

	/**
	 * 求小于本地日期的最近工作日，函数返回日期小于参数日期。
	 * 
	 * @param onlyWeekday 工作日是否必须为周一、周二、周三、周四、周五。
	 * @param localDate   本地日期，非空。
	 * @return 最近工作日，非空。
	 * @throws NullPointerException  非空参数值为空时，抛出此异常。
	 * @throws IllegalStateException 本地日期超出查询范围或者查询失败
	 * @since 1.0
	 */
	default LocalDate before(boolean onlyWeekday, LocalDate localDate) throws IllegalStateException {
		Objects.requireNonNull(localDate);
		return weekday(onlyWeekday, localDate.minusDays(1), false);
	}
}
